//! @file   lc_thread_pthread.cpp
//! @brief  
//! @author Sayan Chaliha
//! @date   September, 2011
//!
//! 
#include <lc_thread.h>
#include <lc_interval.h>
#include <lc_thread_manager.h>
#include <lc_scope_guard.h>

namespace cyan {
  ThreadStorage<Thread*> Thread::threadRef_;

  Thread::Thread(Scope scope, State state, Priority priority, Size stackSize)
      : startCondition_(executionLock_), currentState_(state),
    threadGuard_() {
    Int returnCode = 0;
    this->execution_ = EXECUTION_START_PENDING;
    stackSize = stackSize < PTHREAD_STACK_MIN ?
        PTHREAD_STACK_MIN : stackSize;

    sched_param schedParam;
    Int policy;

    returnCode = ::pthread_getschedparam(::pthread_self(), &policy,
        &schedParam);

    if (returnCode != 0)
      policy = SCHED_OTHER;

    schedParam.sched_priority = Thread::priorityMap(policy, priority);

    if ((returnCode = ::pthread_attr_init(&this->threadAttributes_)) != 0 ||
        (returnCode = ::pthread_attr_setstacksize(&this->threadAttributes_,
            stackSize)) != 0 || 
        (returnCode = ::pthread_attr_setdetachstate(&this->threadAttributes_,
            state)) != 0 ||
        (returnCode = ::pthread_attr_setguardsize(&this->threadAttributes_,
            System::pageSize())) != 0 ||
        (returnCode = ::pthread_attr_setinheritsched(&this->threadAttributes_,
            PTHREAD_EXPLICIT_SCHED)) != 0 ||
        (returnCode = ::pthread_attr_setschedparam(&this->threadAttributes_,
            &schedParam)) != 0 ||
        (returnCode = ::pthread_attr_setschedpolicy(&this->threadAttributes_,
            policy)) != 0) {
      ::pthread_attr_destroy(&this->threadAttributes_);
      System::throwSystemException(returnCode);
    }

    returnCode = ::pthread_attr_setscope(&this->threadAttributes_, scope);
    if (returnCode == ENOTSUP) {
      // Scope specified not supported by the system
      // Try another scope
      scope = scope == SCOPE_LOCAL ? SCOPE_GLOBAL : SCOPE_LOCAL;

      returnCode = ::pthread_attr_setscope(&this->threadAttributes_, scope);

      if (returnCode != 0) {
        ::pthread_attr_destroy(&this->threadAttributes_);
        System::throwSystemException(returnCode);
      }
    }

    returnCode = ::pthread_create(&this->threadHandle_,
        &this->threadAttributes_,
        reinterpret_cast<void* (*)(void*)>(Thread::startPoint), this);

    if (returnCode != 0) {
      ::pthread_attr_destroy(&this->threadAttributes_);
      System::throwSystemException(returnCode);
    }

    if (Thread::STATE_DETACHED == state)
      ThreadManager::registerThread(this);
  }

  Thread::~Thread() {
    CriticalSection section(this->executionLock_);
    WriteGuard guard(this->threadGuard_);
    ::pthread_attr_destroy(&this->threadAttributes_);

    if (Thread::STATE_DETACHED == this->currentState_)
      ThreadManager::unregisterThread(this);
  }

  void Thread::start() {
    CriticalSection section(this->executionLock_);

    if (this->execution_ != EXECUTION_START_PENDING) {
      Exception::throwException(LogicException(TEXT("Invalid state")));
    } else {
      this->execution_ = EXECUTION_STARTED;
      this->startCondition_.notify();
    }
  }

  void Thread::cancel() {
    this->cancelCondtion_.notify();
    if (!this->isStarted())
      this->start();
  }

  void Thread::clearCancellation() {
    this->cancelCondtion_.reset();
  }

  Bool Thread::testCancellation() const {
    return this->cancelCondtion_.wait(Interval::NO_WAIT);
  }

  void Thread::join() {
    assertNotEqual(ThreadManager::getContextualThread(), this);

    if (this == ThreadManager::getContextualThread() || !this->isStarted())
      Exception::throwException(LogicException(TEXT("Invalid state")));

    Int returnCode = ::pthread_join(this->threadHandle_, nullptr);

    if (returnCode != 0)
      System::throwSystemException(returnCode);

    delete this;
  }

  void Thread::detach() {
    Int returnCode = 0;

    if (this->isJoinPending()) {
      this->join();
      return;
    }

    WriteGuard guard(this->threadGuard_);
    returnCode = ::pthread_detach(this->threadHandle_);

    if (returnCode != 0) {
      System::throwSystemException(returnCode);
    } else {
      this->currentState_ = STATE_DETACHED;
      ThreadManager::registerThread(this);
    }
  }

  Thread::Scope Thread::scope() const {
    ReadGuard guard(this->threadGuard_);
    Int returnCode, threadScope;

    returnCode = ::pthread_attr_getscope(&this->threadAttributes_,
        &threadScope);
    if (returnCode != 0)
      System::throwSystemException(returnCode);

    return static_cast<Thread::Scope>(threadScope);
  }

  Thread::Priority Thread::priority() const {
    Int returnCode, policy;
    sched_param schedParam;

    returnCode = ::pthread_getschedparam(this->threadHandle_, &policy,
        &schedParam);
    if (returnCode != 0)
      System::throwSystemException(returnCode);

    return Thread::priorityMap(policy, schedParam.sched_priority);
  }

  void Thread::priority(Priority priority) {
    Int returnCode, policy;
    sched_param schedParam;

    returnCode = ::pthread_getschedparam(this->threadHandle_, &policy,
        &schedParam);

    if (returnCode != 0)
      policy = SCHED_OTHER;

    schedParam.sched_priority = Thread::priorityMap(policy, priority);
    returnCode = ::pthread_setschedparam(this->threadHandle_, policy,
        &schedParam);

    if (returnCode != 0)
      System::throwSystemException(returnCode);
  }

  Thread::State Thread::state() const {
    ReadGuard guard(this->threadGuard_);
    return this->currentState_;
  }

  Bool Thread::isStarted() const {
    CriticalSection section(this->executionLock_);
    return this->execution_ != EXECUTION_START_PENDING;
  }

  Bool Thread::isRunning() const {
    CriticalSection section(this->executionLock_);
    return this->execution_ == EXECUTION_STARTED;
  }

  Bool Thread::isJoinPending() const {
    CriticalSection section(this->executionLock_);
    return this->execution_ == EXECUTION_JOIN_PENDING;
  }

  void Thread::startPoint(void* arg) {
    Thread* thread = reinterpret_cast<Thread*>(arg);
    ScopeGuard scopeGuard(Delegate<void ()>(thread, &Thread::threadEnd));

    if (thread->threadStart())
      thread->process();
  }

  Bool Thread::threadStart() {
    this->executionLock_.lock();
    ThreadManager::setLocalReference(this);
    if (EXECUTION_START_PENDING == this->execution_)
      this->startCondition_.wait(Interval::NO_TIMEOUT);
    this->executionLock_.unlock();
    return !this->testCancellation();
  }

  void Thread::threadEnd() {
    this->executionLock_.lock();
    if (STATE_DETACHED == this->state()) {
      this->executionLock_.unlock();
      delete this;
    } else {
      this->execution_ = EXECUTION_JOIN_PENDING;
      this->executionLock_.unlock();
    }
  }

  Int Thread::priorityMap(Int policy, Priority priority) {
    const Int maxPrio = sched_get_priority_max(policy);
    const Int minPrio = sched_get_priority_min(policy);

    return Int(minPrio + ((maxPrio - minPrio) / 4 * Int(priority)));
  }

  Thread::Priority Thread::priorityMap(Int policy, Int rawPriority) {
    const Int maxPrio = sched_get_priority_max(policy);
    const Int minPrio = sched_get_priority_min(policy);

    return Priority(((rawPriority - minPrio) * 4) / (maxPrio - minPrio));
  }
}
